"""
文件名: youtube_service.py
描述: YouTube API集成服务

本模块包含以下主要功能：
1. YouTube Data API v3的封装和调用
2. 用户订阅频道数据获取
3. 频道详细信息获取
4. 视频数据获取和缓存
5. API配额管理和错误处理

依赖模块:
   - googleapiclient: Google API客户端库
   - google.auth: Google认证
   - httpx: HTTP客户端
   - datetime: 时间处理
   - typing: 类型注解

使用示例:
   >>> youtube_service = YouTubeService()
   >>> await youtube_service.initialize()
   >>> subscriptions = await youtube_service.get_user_subscriptions(user_credentials)
   >>> channel_info = await youtube_service.get_channel_info("UC_channel_id")

注意事项:
   - YouTube API有配额限制，需要合理使用
   - API响应可能包含大量数据，需要分页处理
   - 用户认证凭据需要定期刷新
   - 网络请求要有超时和重试机制
   - 遵循YouTube API使用条款

作者: AI助手
创建日期: 2024-06-27
最后修改: 2024-06-27
版本: 1.0.0
"""

import asyncio
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any, Tuple
import json

import httpx
from google.auth.credentials import Credentials
from google.oauth2.credentials import Credentials as OAuth2Credentials
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

from src.core.config import get_settings
from src.core.logger import get_logger
from src.core.redis import redis_manager
from src.utils.exceptions import (
    YouTubeAPIException,
    QuotaExceededException,
    AuthenticationException,
    RateLimitException,
    ServiceUnavailableException,
)
from src.utils.security import decrypt_token

logger = get_logger(__name__)
settings = get_settings()


class YouTubeService:
    """YouTube API服务类"""
    
    def __init__(self):
        """初始化YouTube服务"""
        self.settings = settings
        self.redis = redis_manager
        self.api_service = None
        self.quota_used_today = 0
        self.max_quota_per_day = 10000  # YouTube API默认配额
        
        # API端点配置
        self.api_base_url = "https://www.googleapis.com/youtube/v3"
        self.api_key = settings.youtube_api_key
        
        # 请求配置
        self.request_timeout = 30  # 请求超时时间（秒）
        self.max_retries = 3  # 最大重试次数
        self.retry_delay = 1  # 重试延迟（秒）
        
        # 缓存配置
        self.cache_ttl = {
            "channel_info": 3600,  # 频道信息缓存1小时
            "subscriptions": 1800,  # 订阅列表缓存30分钟
            "videos": 600,  # 视频信息缓存10分钟
            "playlists": 1800,  # 播放列表缓存30分钟
        }
    
    async def initialize(self) -> None:
        """
        初始化YouTube API服务
        
        Raises:
            ConfigurationException: API密钥未配置时抛出
        """
        if not self.api_key or self.api_key == "demo_api_key_for_testing":
            logger.warning("YouTube API密钥未配置或使用演示配置，部分功能将不可用")
            self._demo_mode = True
        else:
            self._demo_mode = False
        
        # 初始化每日配额使用量
        await self._load_quota_usage()
        
        logger.info("YouTube API服务初始化成功", demo_mode=getattr(self, '_demo_mode', False))
    
    async def _load_quota_usage(self) -> None:
        """加载当日API配额使用情况"""
        try:
            today = datetime.utcnow().strftime("%Y-%m-%d")
            quota_key = f"youtube_quota:{today}"
            
            quota_data = await self.redis.get(quota_key)
            if quota_data:
                self.quota_used_today = quota_data.get("used", 0)
            else:
                self.quota_used_today = 0
                # 设置过期时间为明天凌晨
                tomorrow = datetime.utcnow().replace(
                    hour=0, minute=0, second=0, microsecond=0
                ) + timedelta(days=1)
                expire_seconds = int((tomorrow - datetime.utcnow()).total_seconds())
                
                await self.redis.set(
                    quota_key,
                    {"used": 0, "limit": self.max_quota_per_day},
                    expire=expire_seconds
                )
            
            logger.debug(f"当日YouTube API配额使用情况: {self.quota_used_today}/{self.max_quota_per_day}")
            
        except Exception as e:
            logger.error(f"加载API配额使用情况失败: {e}")
            self.quota_used_today = 0
    
    async def _update_quota_usage(self, cost: int) -> None:
        """
        更新API配额使用情况
        
        Args:
            cost: 本次API调用的配额消耗
        """
        try:
            self.quota_used_today += cost
            
            today = datetime.utcnow().strftime("%Y-%m-%d")
            quota_key = f"youtube_quota:{today}"
            
            await self.redis.set(
                quota_key,
                {
                    "used": self.quota_used_today,
                    "limit": self.max_quota_per_day,
                    "last_updated": datetime.utcnow().isoformat(),
                }
            )
            
            logger.debug(f"API配额使用更新: +{cost}, 总计: {self.quota_used_today}/{self.max_quota_per_day}")
            
        except Exception as e:
            logger.error(f"更新API配额使用情况失败: {e}")
    
    async def _check_quota_limit(self, required_quota: int) -> None:
        """
        检查API配额限制
        
        Args:
            required_quota: 所需配额
            
        Raises:
            QuotaExceededException: 配额不足时抛出
        """
        if self.quota_used_today + required_quota > self.max_quota_per_day:
            raise QuotaExceededException(
                f"YouTube API配额不足，今日已使用: {self.quota_used_today}, "
                f"所需: {required_quota}, 限制: {self.max_quota_per_day}"
            )
    
    async def _make_api_request(
        self,
        endpoint: str,
        params: Dict[str, Any],
        quota_cost: int = 1,
        use_auth: bool = False,
        credentials: Optional[Credentials] = None,
    ) -> Dict[str, Any]:
        """
        发起YouTube API请求
        
        Args:
            endpoint: API端点
            params: 请求参数
            quota_cost: 配额消耗
            use_auth: 是否使用用户认证
            credentials: 用户认证凭据
            
        Returns:
            Dict[str, Any]: API响应数据
            
        Raises:
            YouTubeAPIException: API请求失败时抛出
        """
        # 检查配额
        await self._check_quota_limit(quota_cost)
        
        # 准备请求参数
        if not use_auth:
            params["key"] = self.api_key
        
        # 准备请求头
        headers = {
            "User-Agent": f"{settings.app_name}/{settings.app_version}",
            "Accept": "application/json",
        }
        
        if use_auth and credentials:
            if hasattr(credentials, 'token'):
                headers["Authorization"] = f"Bearer {credentials.token}"
            else:
                raise AuthenticationException("无效的用户认证凭据")
        
        url = f"{self.api_base_url}/{endpoint}"
        
        # 发起请求
        for attempt in range(self.max_retries):
            try:
                async with httpx.AsyncClient(timeout=self.request_timeout) as client:
                    response = await client.get(url, params=params, headers=headers)
                    
                    # 处理HTTP状态码
                    if response.status_code == 200:
                        data = response.json()
                        await self._update_quota_usage(quota_cost)
                        logger.debug(f"YouTube API请求成功: {endpoint}")
                        return data
                    
                    elif response.status_code == 401:
                        raise AuthenticationException("YouTube API认证失败")
                    
                    elif response.status_code == 403:
                        error_data = response.json()
                        error_reason = error_data.get("error", {}).get("errors", [{}])[0].get("reason", "")
                        
                        if error_reason in ["quotaExceeded", "dailyLimitExceeded"]:
                            raise QuotaExceededException("YouTube API配额已用完")
                        else:
                            raise YouTubeAPIException(f"YouTube API权限错误: {error_reason}")
                    
                    elif response.status_code == 429:
                        raise RateLimitException("YouTube API请求频率超限")
                    
                    elif response.status_code >= 500:
                        if attempt < self.max_retries - 1:
                            await asyncio.sleep(self.retry_delay * (2 ** attempt))
                            continue
                        else:
                            raise ServiceUnavailableException("YouTube API服务器错误")
                    
                    else:
                        raise YouTubeAPIException(f"YouTube API请求失败: HTTP {response.status_code}")
            
            except httpx.TimeoutException:
                if attempt < self.max_retries - 1:
                    await asyncio.sleep(self.retry_delay * (2 ** attempt))
                    continue
                else:
                    raise YouTubeAPIException("YouTube API请求超时")
            
            except httpx.RequestError as e:
                if attempt < self.max_retries - 1:
                    await asyncio.sleep(self.retry_delay * (2 ** attempt))
                    continue
                else:
                    raise YouTubeAPIException(f"YouTube API网络错误: {str(e)}")
    
    async def get_user_subscriptions(
        self,
        credentials: Credentials,
        max_results: int = 50,
        order: str = "alphabetical",
    ) -> List[Dict[str, Any]]:
        """
        获取用户的订阅频道列表
        
        Args:
            credentials: 用户认证凭据
            max_results: 最大返回数量（每页）
            order: 排序方式（alphabetical, relevance, unread, activity）
            
        Returns:
            List[Dict[str, Any]]: 订阅频道列表
            
        Raises:
            YouTubeAPIException: API请求失败时抛出
        """
        try:
            # 检查缓存
            cache_key = f"user_subscriptions:{getattr(credentials, 'token', 'unknown')[:10]}"
            cached_data = await self.redis.get(cache_key)
            if cached_data:
                logger.debug("从缓存获取用户订阅列表")
                return cached_data
            
            all_subscriptions = []
            next_page_token = None
            
            while True:
                params = {
                    "part": "snippet,contentDetails",
                    "mine": "true",
                    "maxResults": min(max_results, 50),  # API最大支持50
                    "order": order,
                }
                
                if next_page_token:
                    params["pageToken"] = next_page_token
                
                # 发起API请求
                response = await self._make_api_request(
                    "subscriptions",
                    params,
                    quota_cost=1,
                    use_auth=True,
                    credentials=credentials,
                )
                
                # 处理响应数据
                items = response.get("items", [])
                all_subscriptions.extend(items)
                
                # 检查是否还有更多页面
                next_page_token = response.get("nextPageToken")
                if not next_page_token or len(all_subscriptions) >= max_results:
                    break
            
            # 限制返回数量
            all_subscriptions = all_subscriptions[:max_results]
            
            # 缓存结果
            await self.redis.set(
                cache_key,
                all_subscriptions,
                expire=self.cache_ttl["subscriptions"]
            )
            
            logger.info(f"获取用户订阅列表成功，共 {len(all_subscriptions)} 个频道")
            return all_subscriptions
            
        except (YouTubeAPIException, AuthenticationException):
            raise
        except Exception as e:
            logger.error(f"获取用户订阅列表失败: {e}")
            raise YouTubeAPIException(f"获取订阅列表失败: {str(e)}")
    
    async def get_channel_info(
        self,
        channel_id: str,
        include_statistics: bool = True,
        include_branding: bool = False,
    ) -> Dict[str, Any]:
        """
        获取频道详细信息
        
        Args:
            channel_id: 频道ID
            include_statistics: 是否包含统计信息
            include_branding: 是否包含品牌信息
            
        Returns:
            Dict[str, Any]: 频道详细信息
            
        Raises:
            YouTubeAPIException: API请求失败时抛出
        """
        try:
            # 检查缓存
            cache_key = f"channel_info:{channel_id}"
            cached_data = await self.redis.get(cache_key)
            if cached_data:
                logger.debug(f"从缓存获取频道信息: {channel_id}")
                return cached_data
            
            # 构建请求部分
            parts = ["snippet", "contentDetails"]
            if include_statistics:
                parts.append("statistics")
            if include_branding:
                parts.append("brandingSettings")
            
            params = {
                "part": ",".join(parts),
                "id": channel_id,
            }
            
            # 发起API请求
            response = await self._make_api_request(
                "channels",
                params,
                quota_cost=1,
            )
            
            # 检查响应
            items = response.get("items", [])
            if not items:
                raise YouTubeAPIException(f"频道不存在或无法访问: {channel_id}")
            
            channel_info = items[0]
            
            # 缓存结果
            await self.redis.set(
                cache_key,
                channel_info,
                expire=self.cache_ttl["channel_info"]
            )
            
            logger.debug(f"获取频道信息成功: {channel_id}")
            return channel_info
            
        except YouTubeAPIException:
            raise
        except Exception as e:
            logger.error(f"获取频道信息失败: {e}", channel_id=channel_id)
            raise YouTubeAPIException(f"获取频道信息失败: {str(e)}")
    
    async def get_channel_videos(
        self,
        channel_id: str,
        max_results: int = 10,
        published_after: Optional[datetime] = None,
        order: str = "date",
    ) -> List[Dict[str, Any]]:
        """
        获取频道的最新视频
        
        Args:
            channel_id: 频道ID
            max_results: 最大返回数量
            published_after: 发布时间过滤（可选）
            order: 排序方式（date, rating, relevance, title, viewCount）
            
        Returns:
            List[Dict[str, Any]]: 视频列表
            
        Raises:
            YouTubeAPIException: API请求失败时抛出
        """
        try:
            # 检查缓存
            cache_key = f"channel_videos:{channel_id}:{max_results}:{order}"
            if published_after:
                cache_key += f":{published_after.isoformat()}"
            
            cached_data = await self.redis.get(cache_key)
            if cached_data:
                logger.debug(f"从缓存获取频道视频: {channel_id}")
                return cached_data
            
            params = {
                "part": "snippet",
                "channelId": channel_id,
                "type": "video",
                "order": order,
                "maxResults": min(max_results, 50),
            }
            
            if published_after:
                params["publishedAfter"] = published_after.isoformat() + "Z"
            
            # 发起API请求
            response = await self._make_api_request(
                "search",
                params,
                quota_cost=100,  # search API消耗较多配额
            )
            
            videos = response.get("items", [])
            
            # 如果需要详细信息，再次请求videos端点
            if videos:
                video_ids = [item["id"]["videoId"] for item in videos]
                detailed_videos = await self._get_videos_details(video_ids)
                
                # 合并数据
                for i, video in enumerate(videos):
                    video_id = video["id"]["videoId"]
                    if video_id in detailed_videos:
                        video.update(detailed_videos[video_id])
            
            # 缓存结果
            await self.redis.set(
                cache_key,
                videos,
                expire=self.cache_ttl["videos"]
            )
            
            logger.debug(f"获取频道视频成功: {channel_id}, 共 {len(videos)} 个视频")
            return videos
            
        except YouTubeAPIException:
            raise
        except Exception as e:
            logger.error(f"获取频道视频失败: {e}", channel_id=channel_id)
            raise YouTubeAPIException(f"获取频道视频失败: {str(e)}")
    
    async def _get_videos_details(self, video_ids: List[str]) -> Dict[str, Dict[str, Any]]:
        """
        获取视频详细信息
        
        Args:
            video_ids: 视频ID列表
            
        Returns:
            Dict[str, Dict[str, Any]]: 视频详细信息字典，key为video_id
        """
        try:
            if not video_ids:
                return {}
            
            # 分批处理（API限制每次最多50个ID）
            batch_size = 50
            all_videos = {}
            
            for i in range(0, len(video_ids), batch_size):
                batch_ids = video_ids[i:i + batch_size]
                
                params = {
                    "part": "snippet,statistics,contentDetails",
                    "id": ",".join(batch_ids),
                }
                
                response = await self._make_api_request(
                    "videos",
                    params,
                    quota_cost=1,
                )
                
                for item in response.get("items", []):
                    all_videos[item["id"]] = item
            
            return all_videos
            
        except Exception as e:
            logger.error(f"获取视频详细信息失败: {e}")
            return {}
    
    async def refresh_user_credentials(self, refresh_token: str) -> Optional[Credentials]:
        """
        刷新用户的OAuth凭据
        
        Args:
            refresh_token: 刷新令牌
            
        Returns:
            Optional[Credentials]: 新的认证凭据，失败返回None
        """
        try:
            async with httpx.AsyncClient() as client:
                data = {
                    "client_id": settings.google_client_id,
                    "client_secret": settings.google_client_secret,
                    "refresh_token": refresh_token,
                    "grant_type": "refresh_token",
                }
                
                response = await client.post(
                    "https://oauth2.googleapis.com/token",
                    data=data,
                    timeout=10.0,
                )
                
                if response.status_code == 200:
                    token_data = response.json()
                    
                    # 创建新的认证凭据
                    credentials = OAuth2Credentials(
                        token=token_data["access_token"],
                        refresh_token=refresh_token,
                        id_token=token_data.get("id_token"),
                        token_uri="https://oauth2.googleapis.com/token",
                        client_id=settings.google_client_id,
                        client_secret=settings.google_client_secret,
                    )
                    
                    logger.info("用户OAuth凭据刷新成功")
                    return credentials
                
                else:
                    logger.error(f"刷新OAuth凭据失败: HTTP {response.status_code}")
                    return None
        
        except Exception as e:
            logger.error(f"刷新OAuth凭据异常: {e}")
            return None
    
    async def get_quota_usage(self) -> Dict[str, Any]:
        """
        获取API配额使用情况
        
        Returns:
            Dict[str, Any]: 配额使用信息
        """
        try:
            today = datetime.utcnow().strftime("%Y-%m-%d")
            quota_key = f"youtube_quota:{today}"
            
            quota_data = await self.redis.get(quota_key)
            if not quota_data:
                quota_data = {"used": 0, "limit": self.max_quota_per_day}
            
            return {
                "used": quota_data["used"],
                "limit": quota_data["limit"],
                "remaining": quota_data["limit"] - quota_data["used"],
                "percentage": (quota_data["used"] / quota_data["limit"]) * 100,
                "date": today,
            }
            
        except Exception as e:
            logger.error(f"获取配额使用情况失败: {e}")
            return {
                "used": 0,
                "limit": self.max_quota_per_day,
                "remaining": self.max_quota_per_day,
                "percentage": 0,
                "date": datetime.utcnow().strftime("%Y-%m-%d"),
            }


# 导出主要接口
__all__ = [
    "YouTubeService",
]